/**************************************************************************(C)*
 *
 *  This file is part of the GreenHills project
 *  Copyright (C) 2009  Tieto Corporation
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  Not for commmercial use. Only with Tieto permission.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************(C)*/

#include "../serialport.h"

#include <QMutexLocker>
#include <QMetaType>

#if defined Q_OS_WIN32
    #include <windows.h>
    #include <string>
#elif defined Q_OS_WINCE
    #include <Winbase.h>
    #include <string>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>

using namespace greenhills::util;

class CSerialPort::Private
{
public:
    Private(CSerialPort *publ);
    virtual ~Private();

friend class CSerialPort;

private:
    static QMutex mMutex;
    static HANDLE mPortHandle;
    static CSerialPort *mSerialPortInstance;
};

QMutex CSerialPort::Private::mMutex;
HANDLE CSerialPort::Private::mPortHandle = INVALID_HANDLE_VALUE;
CSerialPort *CSerialPort::Private::mSerialPortInstance = 0;

//==============================================================================
// Private part
//==============================================================================
CSerialPort::Private::Private(CSerialPort *publ)
    //:mPublic(*publ)
{    
    mSerialPortInstance = publ;
}

CSerialPort::Private::~Private()
{
}

//==============================================================================
// Public part
//==============================================================================
CSerialPort *CSerialPort::instance()
{
    static CSerialPort *serialPortInstance = new CSerialPort();
    return serialPortInstance;
}

CSerialPort::CSerialPort()
    :QThread(),mPrivate(*new Private(this))
{
    moveToThread(this);
}

CSerialPort::~CSerialPort()
{
    delete &mPrivate;
}

bool CSerialPort::open(const char *device, int baudrate)
{
    QMutexLocker locker(&mPrivate.mMutex);
    if (mPrivate.mPortHandle != INVALID_HANDLE_VALUE)
        return false;

    QString dev(device);
    HANDLE handle = CreateFile((const WCHAR*)dev.utf16(), GENERIC_READ | GENERIC_WRITE, 0, 0,
            OPEN_EXISTING, 0, NULL);

    if (handle == INVALID_HANDLE_VALUE)
        return false;

    if (baudrate != 0)
    {
        DCB *dcb = (DCB*)malloc(sizeof(DCB)+1024);
        memset(dcb, sizeof(sizeof(DCB)+1024), 0);
        dcb->DCBlength=sizeof(DCB)+1024;

        //if (!BuildCommDCB(L"4800,n,8,1", &dcbSerialParams))
        //    return false;

        if (!GetCommState(handle, dcb))
        {
            int x = GetLastError();
            if (x>0)
                return false;
        }

        dcb->BaudRate=baudrate;//CBR_4800;
        dcb->ByteSize=8;
        dcb->StopBits=ONESTOPBIT;
        dcb->Parity=NOPARITY;
        if(!SetCommState(handle, dcb))
            return false;

        free(dcb);
    }

    COMMTIMEOUTS timeouts={0};
    timeouts.ReadIntervalTimeout=50;
    timeouts.ReadTotalTimeoutConstant=50;
    timeouts.ReadTotalTimeoutMultiplier=10;

    if(!SetCommTimeouts(handle, &timeouts))
        return false;

    mPrivate.mPortHandle = handle;
    start();
    return true;
}

bool CSerialPort::close()
{
    terminate();
    wait(1000);

    QMutexLocker locker(&mPrivate.mMutex);

    if (mPrivate.mPortHandle == 0)
        return false;

    CloseHandle(mPrivate.mPortHandle);

    mPrivate.mPortHandle = INVALID_HANDLE_VALUE;
    return true;
}

void CSerialPort::run()
{
    //! \todo Use WaitForSingleObject here (as described in http://msdn.microsoft.com/en-us/library/ms810467.aspx)
    DWORD dwRead;

    #define READ_BUF_SIZE 1024
    char lpBuf[READ_BUF_SIZE];

    forever
    {
        BOOL fWaitingOnRead = FALSE;
        OVERLAPPED osReader = {0};

        // Create the overlapped event. Must be closed before exiting
        // to avoid a handle leak.
        osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

        // Error creating overlapped event; abort.
        if (osReader.hEvent == NULL)
            return;

        if (!fWaitingOnRead)
        {
            // Issue read operation.
            if (!ReadFile(mPrivate.mPortHandle, lpBuf, READ_BUF_SIZE, &dwRead, &osReader))
            {
                if (GetLastError() != ERROR_IO_PENDING)     // read not delayed?
                    continue;
                else
                    fWaitingOnRead = TRUE;
            }
            else
            {
                // read completed immediately
                Q_EMIT dataReceived(QByteArray(lpBuf, dwRead));
            }
        }
    }
}
